#include "sweepwidget.h"

SweepWidget::SweepWidget(QWidget *parent)
        :QGLWidget(parent)
{
    setFormat(QGLFormat(QGL::DoubleBuffer | QGL::DepthBuffer | QGL::DirectRendering));
    this->setAutoBufferSwap(true);
    this->setFocusPolicy(Qt::StrongFocus);

    defaultCam = Camera(40.0 , 0.0 , 0.0 , 0.0 , 0.0 , 0.0 , 1.0);
    cam = Camera(defaultCam);
    axisCam = Camera(40.0 , 0.0 , 0.0 , 20.0 , 0.0 , 0.0 , 0.0);
    timer = new QTime();
    showNormals = false;
}

SweepWidget::~SweepWidget(){}

void SweepWidget::initializeGL()
{
    qglClearColor(Qt::blue);
    glShadeModel(GL_SMOOTH);
    glEnable(GL_DEPTH_TEST);

    glHint(GL_LINE_SMOOTH_HINT , GL_NICEST);
    glHint(GL_POLYGON_SMOOTH_HINT , GL_NICEST);
    glEnable(GL_LINE_SMOOTH);
    glEnable(GL_POLYGON_SMOOTH);
    glLineWidth(0.01);

    initLightingModel();
    poly = readPoly(QString("torus.swp"));
}

void SweepWidget::initLightingModel(){
    //Light and material parameters.

    GLfloat light_ambient[] = {0.0 , 0.0 , 0.0 , 0.0};
    glLightfv(GL_LIGHT0 , GL_AMBIENT , light_ambient);

    GLfloat material_ambient[] = {0.11 , 0.06 , 0.11 , 1.00};
    GLfloat material_difuse[] = {0.43 , 0.47 , 0.54 , 1.00};
    GLfloat material_specular[] = {0.33 , 0.33 , 0.52 , 1.00};
    GLfloat material_shininess = 10;

    glMaterialfv(GL_FRONT , GL_AMBIENT , material_ambient);
    glMaterialfv(GL_FRONT , GL_DIFFUSE , material_difuse);
    glMaterialfv(GL_FRONT , GL_SPECULAR , material_specular);
    glMaterialf(GL_FRONT , GL_SHININESS , material_shininess);

    glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER , GL_TRUE);
    glLightModeli(GL_LIGHT_MODEL_TWO_SIDE , GL_TRUE);

    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
}

void SweepWidget::resizeGL( int w, int h )
{
    this->w = w;
    this->h = h;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    GLfloat x = GLfloat(w) / h;
    gluPerspective( 40.0, x, 10.0, 500.0 );
}

void SweepWidget::paintGL()
{
    glViewport(0, 0, w, h);

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    //The light will move with the camera because the viewing transformation is applied after.
    GLfloat light_pos[] = {0.0 , 0.0 , 0.0 , 1.0};
    glLightfv(GL_LIGHT0 , GL_POSITION , light_pos);

    gluLookAt(cam.eyeX , cam.eyeY , cam.eyeZ ,
              cam.eyeX + cam.lx , cam.eyeY + cam.ly , cam.eyeZ + cam.lz ,
              0.0 , 1.0 , 0.0);

    poly.draw(showNormals);
    drawCoordSystemReferences();
}

void SweepWidget::drawCoordSystemReferences(){
    //Draws the origin, x, y and z axis.

    glClear(GL_DEPTH_BUFFER_BIT);
    glViewport(0, 0, w/5, h/5);
    glLoadIdentity();

    gluLookAt(axisCam.eyeX , axisCam.eyeY , axisCam.eyeZ ,
              axisCam.lx, axisCam.ly , axisCam.lz ,
              0.0 , 1.0 , 0.0);

    glDisable(GL_LIGHTING);

    GLUquadric *quadric = gluNewQuadric();

    glColor3f(1.0 , 1.0 , 1.0);
    gluSphere(quadric , 1.0 , 10 , 10);

    //Z Axis
    glColor3f(1.0 , 0.0 , 0.0);
    drawAxis(quadric);

    //X Axis
    glRotatef(90.0 , 0.0 , 1.0 , 0.0);
    glColor3f(0.0 , 1.0 , 0.0);
    drawAxis(quadric);

    //Y Axis
    glRotatef(-90.0 , 1.0 , 0.0 , 0.0);
    glColor3f(0.0 , 1.0 , 1.0);
    drawAxis(quadric);

    glEnable(GL_LIGHTING);
}

void SweepWidget::drawAxis(GLUquadric *quadric){

    glBegin(GL_LINES);
        glVertex3f(0.0 , 0.0 , 0.0);
        glVertex3f(0.0 , 0.0 , 5.0);
    glEnd();
    glPushMatrix();
    glTranslatef(0.0 , 0.0 , 4.0);
    gluCylinder(quadric , 1.0 , 0.0 , 2.0 , 10 , 10);
    glPopMatrix();
}

SweepPolyhedra SweepWidget::readPoly(QString fileName)
{   //Read the instructions to construct the polyhedra and initialize it.

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    QFile polyFile(fileName);

    SweepPolyhedra poly;

    polyFile.open(QIODevice::ReadOnly | QIODevice::Text);

    QTextStream in(&polyFile);
    in.setRealNumberNotation(QTextStream::FixedNotation);

    BaseCurve baseCurve(readBaseCurve(in));
    trajectory = readTrajectory(in);
    Trajectory processedTrajectory(processTrajectory(trajectory));

    polyFile.close();

    this->baseCurve = baseCurve;
    poly = SweepPolyhedra(baseCurve , processedTrajectory);

    return poly;
}

BaseCurve SweepWidget::readBaseCurve(QTextStream &in){
    //base curve reading
    double coords[3];
    int nPoints;
    Points points;
    QString thrash;

    in >> thrash >> nPoints >> ws;
    for(int i = 0 ; i < nPoints ; i++){
        readCoords(in , coords);
        points.push_back(Point_3(coords[0] , coords[1] , coords[2]));
    }

    BaseCurve baseCurve(points);

    //pointing the Camera to the end of the poly
    defaultCam.pointTo(coords[0] , coords[1] , coords[2]);
    cam = defaultCam;

    return baseCurve;
}

Trajectory SweepWidget::readTrajectory(QTextStream &in){
    Trajectory trajectory;
    QChar type;
    QString thrash;
    double angle , coords[3];

    while(!in.atEnd()){

        in >> type;

        if(type == QChar('P')){
            trajectory.push_back(Instruction(Instruction::PLOTING));
        }
        else{
            if(type == QChar('R')){
                in >> angle >> thrash;
                readCoords(in , coords);
                trajectory.push_back(Instruction(Instruction::ROTATION , coords , angle));
            }
            else{
                if(type == QChar('T')){
                    readCoords(in , coords);
                    trajectory.push_back(Instruction(Instruction::TRANSLATION , coords));
                }
                else{
                    if(type == QChar('S')){
                        readCoords(in , coords);
                        trajectory.push_back(Instruction(Instruction::SCALING , coords));
                    }
                }
            }
        }
    }
    return trajectory;
}

Trajectory SweepWidget::processTrajectory(Trajectory trajectory){
    //Process the trajectory and returns a deque with Instructions that are only generic transformations.

    Trajectory processedTrajectory;
    Instruction instr;
    double glTransform[16];

    glPushMatrix();
    while(!trajectory.empty()){
        do{
            instr = trajectory.front();
            trajectory.pop_front();
            instr.execute();
        }while(instr.type != Instruction::PLOTING && !trajectory.empty());
        glGetDoublev(GL_MODELVIEW_MATRIX , glTransform);
        processedTrajectory.push_back(Instruction(Instruction::GENERIC_TRANSFORMATION , 0 , 0.0 , glTransform));
    }
    glPopMatrix();
    return processedTrajectory;
}

void SweepWidget::readCoords(QTextStream &in , double* coords){
    QString line = in.readLine();
    QStringList list = line.split(" ; ");

    for(int i = 0 ; i < 3 ; i++){
        coords[i] = list.takeFirst().toDouble();
    }
}

void SweepWidget::mousePressEvent(QMouseEvent *event)
{
    lastPos = event->pos();
    timer->restart();
}

void SweepWidget::mouseMoveEvent(QMouseEvent *event)
{
    double dt , dtX , dtY , rotationAtenuation = -0.005 , panXAtenuation = -0.5 , panYAtenuation = 0.5;

    dt = (double)timer->restart() / 1000;

    dtX = dt * (event->x() - lastPos.x());
    dtY = dt * (event->y() - lastPos.y());

    if (event->buttons() & Qt::LeftButton) {
        cam.rotate(dtX * rotationAtenuation , cam.Y_AXIS);
        cam.rotate(dtY * rotationAtenuation , cam.X_AXIS);
    } else{
        if (event->buttons() & Qt::RightButton) {
            cam.pan(dtX * panXAtenuation , cam.X_AXIS);
            cam.pan(dtY * panYAtenuation , cam.Y_AXIS);
        }
        else{
            if(event->buttons() & Qt::MidButton){
                cam.zoom(dtY);
            }
        }
    }
    updateAxisCam();
    updateGL();
    lastPos = event->pos();
}

void SweepWidget::keyPressEvent(QKeyEvent *event){

    double rotationAtenuation = 0.025 , panXAtenuation = 10.0 , panYAtenuation = 1.0 , zoomAtenuation = 2.0;
    double dt = (double)timer->restart() / 1000;

    if(dt > 0.5){
        dt = 0.5;
    }

    int key;
    key = event->key();

    switch(key){
        case Qt::Key_Up: cam.pan(dt * panYAtenuation, cam.Y_AXIS); break;

        case Qt::Key_Down: cam.pan(-dt * panYAtenuation , cam.Y_AXIS); break;

        case Qt::Key_Left:
            cam.pan(-dt * panXAtenuation , cam.X_AXIS); break;

        case Qt::Key_Right:
            cam.pan(dt * panXAtenuation , cam.X_AXIS); break;

        case Qt::Key_W:
            cam.rotate(dt * rotationAtenuation , cam.X_AXIS); break;

        case Qt::Key_S:
            cam.rotate(-dt * rotationAtenuation , cam.X_AXIS); break;

        case Qt::Key_A:
            cam.rotate(-dt * rotationAtenuation , cam.Y_AXIS); break;

        case Qt::Key_D:
            cam.rotate(dt * rotationAtenuation , cam.Y_AXIS); break;

        case Qt::Key_Equal:
            cam.zoom(dt * zoomAtenuation); break;

        case Qt::Key_Minus:
            cam.zoom(-dt * zoomAtenuation); break;

        case Qt::Key_R:
            resetCam(); break;
    }

    updateAxisCam();
    updateGL();
}

void SweepWidget::resetCam(){
    cam = defaultCam;
    updateAxisCam();
    updateGL();
}

void SweepWidget::updateAxisCam(){
    //Changes the axisCam position. This camera is always on the surrounding sphere of the origin and points to it.
    double radix = 20.0;

    Vector_3 camDist(cam.eyeX , cam.eyeY , cam.eyeZ);
    //normalizing the vector
    camDist = camDist / sqrt(camDist * camDist);

    camDist = camDist * radix;
    axisCam.eyeX = camDist.x(); axisCam.eyeY = camDist.y(); axisCam.eyeZ = camDist.z();
}
